#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <assert.h>

#include <windows.h>
#include <shlwapi.h>

const char * root_key_names[] =
{
  "HKEY_CLASSES_ROOT",
  "HKEY_CURRENT_CONFIG",
  "HKEY_CURRENT_USER",
  "HKEY_DYN_DATA",
  "HKEY_LOCAL_MACHINE",
  "HKEY_PERFORMANCE_DATA",
  "HKEY_USERS"
};
const HKEY root_keys[] =
{
  HKEY_CLASSES_ROOT,
  HKEY_CURRENT_CONFIG,
  HKEY_CURRENT_USER,
  HKEY_DYN_DATA,
  HKEY_LOCAL_MACHINE,
  HKEY_PERFORMANCE_DATA,
  HKEY_USERS
};
const int ROOT_KEYS_MAX = sizeof(root_key_names) / sizeof(root_key_names[0]);
const int ROOT_KEYS_MAX2 = sizeof(root_keys) / sizeof(root_keys[0]);

int regread(char * file);
void chomp(char * str);
int get_root_key(const char * key, HKEY * root, const char ** subkey);
int print_reg_key(const char * key, const char * name);
void print_value(const unsigned char * value, const DWORD type,
                const DWORD length);

int main(int argc, char *argv[])
{
  int i;
  int r = 0;

  /* Sanity check */
  assert(ROOT_KEYS_MAX == ROOT_KEYS_MAX2);

  for (i = 1; i < argc; i++)
    r |= regread(argv[i]);

  if (argc == 1)
    r |= regread("-");

  return r;
}

/* Read filename line by line until EOF, every pair of lines represent the
 * registry key and name.
 * Returns non-zero on error
 */
int regread(char * filename)
{
  int r;
  FILE * fs = NULL;
  char * return_string;
  char key[2048];
  char name[2048];

  /* Get the file stream */
  if (strcmp(filename, "-") == 0)
  {
    fs = stdin;
  }
  else
  {
    fs = fopen(filename, "r");
    if (!fs)
    {
      perror("fopen");
      return 1;
    }
  }

  while (1)
  {
    /* Read in the key */
    return_string = fgets(key, sizeof(key), fs);
    if (!return_string)
      break;

    chomp(key);

    /* Read in the name */
    return_string = fgets(name, sizeof(name), fs);
    if (!return_string)
      break;

    chomp(name);

    r = print_reg_key(key, name);
    if (r)
    {
      /* Ok, something went wrong there */
      fprintf(stderr, "%s: error %d\n", __FUNCTION__, r);
      printf("\n");
    }
  }

  /* Close the file on the way out */
  if (fs != stdin)
    fclose(fs);

  return 0;
}

/* Gets rid of the trailing \r and \n */
void chomp(char * str)
{
  int len;

  if (!str)
    return;

  len = strlen(str);
  if (len < 1)
    return;

  if (str[len - 1] == '\n')
  {
    if (len > 1 && str[len - 2] == '\r')
      str[len - 2] = 0;
    else
      str[len - 1] = 0;
  }
  else
  {
    fprintf(stderr, "%s: Didn't find return character\n", __FUNCTION__);
  }
}

/* key (in) - the key we are looking for
 * root (out) - value for the root key
 * subkey (out) - the key without the root key
 *
 * returns 0 on success, 1 on error
 */
int get_root_key(const char * key, HKEY * root, const char ** subkey)
{
  int i;
  int r;
  int len;
  int slash = -1;

  if (key == NULL)
    return 1;

  /* Find the '\' */
  len = strlen(key);
  for (i = 0; i < len; i++)
  {
    if (key[i] == '\\')
    {
      slash = i;
      break;
    }
  }

  /* Check if the root key is too short/long */
  if (slash < strlen("HKEY_USERS") || slash > strlen("HKEY_PERFORMANCE_DATA"))
    return 1;

  /* Cycle through all the possible root keys */
  for (i = 0; i < ROOT_KEYS_MAX; i++)
  {
    const int l = strlen(root_key_names[i]);

    /* Does the string length match */
    if (slash != l)
      continue;

    r = strncmp(root_key_names[i], key, l);
    if (r == 0)
    {
      /* We found a match */
      *root = root_keys[i];
      *subkey = key + slash + 1;
      return 0;
    }
  }

  return 1;
}

/* Finds and prints the value for a given key and name
 * "" represents the default name
 * Returns 0 on success
 */
int print_reg_key(const char * key, const char * name)
{
  int r;
  const char * subkey = NULL;
  unsigned char * value;
  HKEY root;
  DWORD value_len;
  DWORD value_type;

  r = get_root_key(key, &root, &subkey);
  if (r)
    return r;

  value_len = 0;
  value = NULL;
  /* SHGetValue will return the length of the value in value_len */
  r = SHGetValue(root, subkey, name, &value_type, &value, &value_len);
  if (r == ERROR_MORE_DATA)
  {
    /* We can malloc now that we know what value_len is */
    value = malloc(value_len);
    if (!value)
      return 1;

    r = SHGetValue(root, subkey, name, &value_type, value, &value_len);
    if (!r)
      print_value(value, value_type, value_len);
  }
  else
    r = 1;

  return r;
}

/* Prints value based on its type */
void print_value(const unsigned char * value, const DWORD type,
                const DWORD length)
{
  int i;

  if (type == REG_SZ)
  {
    printf("%s\n", value);
    return;
  }

  for (i = 0; i < length - 1; i++)
  {
    printf("%02X, ", value[i]);
  }
  printf("%02X\n", value[i]);
}
